<?php

namespace app\file\controller;
use app\file\model\File as FileModel;
use think\Request;
use think\File;
use constant\Ota as OtaType;

/**
 * 文件管理
 *
 * @author 梁伟明 <LiangWeiMing 61843912@qq.com>
 */
class FileManager {
	
	//根目录（相对路径）
	protected $base			= './Uploads/';
	//根目录（绝对路径）
	protected $root			= ROOT_PATH;
	//文件类型
	protected $fileType		= array(
		'file'				=> 'file',
		'img'				=> 'img',
		'ota'				=> 'ota',
		'apk'				=> 'apk',
		'zip'				=> 'zip'
	);
	//文件名生成规则集
	protected $nameRule		= array('date', 'md5', 'sha1');
	//默认文件名规则
	protected $rule			= 'date';
	//是否生成文件md5值
	protected $md5			= true;

	protected $returnInfo	= array(
		'ota'				=> 'parseOtaPackage',
		'apk'				=> 'parseApk'
	);
	
	protected $request		= null;

	protected $ajaxResult	= array(
		'status'			=> 0,
		'msg'				=> ''
	);
	
	public function __construct() {
		$this->request		= Request::instance();
	}

	public function index(){
		return view();
	}
	
	public function upload(){
		$params			= $this->getParams();
		if(!$params['files']){
			$this->ajaxResult['msg'] = '没有要上传的文件';
			return json_encode($this->ajaxResult);
		}
		if(isset($params['name_rule']) && in_array($params['name_rule'], $this->nameRule)){
			$this->rule  = $params['name_rule'];
		}
		$file_type = '';
		if(isset($params['file_type'])){
			$file_type = $params['file_type'];
		}
		
		$this->ajaxResult['status']		= 1;
		$this->ajaxResult['total']		= count($params['files']);
		$this->ajaxResult['data']		= array();
		$this->ajaxResult['success']	= 0;
		$this->ajaxResult['fail']		= array();
		foreach($params['files'] as $name => $file){
			$upload = $this->_doUpload($file, $file_type);
			//上传成功
			if($upload){
				$insert		= array(
					'name'			=> $upload->getFilename(),
					'origin_name'	=> $upload->origin_name,
					'size'			=> $upload->getSize(),
					'ext'			=> $upload->getExtension(),
					'type'			=> $upload->file_type,
					'absolute_path'	=> $upload->getRealPath(),
					'relative_path'	=> $upload->getPathname(),		
				);
				if($this->md5){
					$insert['md5']  = $upload->md5();
				}
				$db_result = $this->insertDb($insert);
				if($db_result){
					$this->ajaxResult['success'] ++;
					$this->ajaxResult['data'][$name] = $db_result;
				} 
			} else {
				$this->ajaxResult['fail'][] = $name;
			}
		}

		return json_encode($this->ajaxResult);
	}
	
	/**
	 * 获取参数
	 * @return array
	 */
	protected function getParams(){
		$params			= $this->request->param();
		$files			= $this->request->file();
		$return			= [$params, 'files' => $files];
		return $return;
	}
	
	/**
	 * 上传文件
	 * @param File $file
	 * @param string $file_type
	 * @return File
	 */
	private function _doUpload(File $file, $file_type = ''){				
		if($this->rule == 'date'){
			$file->rule(__NAMESPACE__ . '\FileManager::makeDateName');
		} else {
			$file->rule($this->rule);
		}
		//获取文件类型
		if(!$file_type || !in_array($file_type, $this->fileType)){
			$ext = pathinfo($file->getInfo('name'), PATHINFO_EXTENSION);
			$file_type = $this->getFileType($ext);
		}
		$origin_name			= $file->getInfo('name');
		
		$upload					= $file->move($this->base . $file_type);
		
		$upload->file_type		= $file_type;
		$upload->origin_name	= $origin_name;
		return $upload;
		
	}
	
	public static function makeDateDir(){
		return date('Y/m/');
	}


	public static function makeDateName(){
		$savename = self::makeDateDir() . date('Ymd') . microtime(true)*10000 . self::getRandNum();	
		return $savename;
	}
	
	public static function getRandNum($length = 4){
		$chars = array(
			0, 1, 2, 3, 4, 5, 6, 7, 8, 9
		);
		$return = "";
		for($i = 0; $i < $length; $i++){
			$return .= $chars[mt_rand(0 ,9)];
		}
		return $return;
	}
	
	/**
	 * 获取文件类型
	 * @param string $ext 文件扩展名
	 * @return string
	 */
	public function getFileType($ext){
		$type = '';
		$img = array('jpg', 'jpeg', 'png', 'gif' , 'bmp');
		$zip = array('zip', 'rar', 'tar', 'iso');
		$apk = array('apk');
		if(in_array($ext, $img)){
			$type = $this->fileType['img'];
		} else if(in_array($ext, $zip)){
			$type = $this->fileType['zip'];
		} else if(in_array($ext, $apk)){
			$type = $this->fileType['apk'];
		} else {
			$type = $this->fileType['file'];
		}
		return $type;
	}
	
	/**
	 * 将上传结果写入数据库
	 * @param array $data （上传成功之后的文件）
	 */
	public function insertDb($data = array()){
		$now = time();
		$data['create_time']	= $now;
		$data['update_time']	= $now;	
		$new = new FileModel;
		$new->data($data)->save();
		return $new;
	}
	
	/**
	 * 检查文件是否存在（MD5）
	 */
	public function checkFileMd5($md5 = ''){
		$req_md5 = $this->request->request('md5', '', 'trim');
		if($req_md5){
			$md5 = $req_md5;
		}

		$res = FileModel::get(['md5' => $md5]);
		if($res && is_file($res['absolute_path'])){
			$this->ajaxResult['status'] = 0;
			$this->ajaxResult['msg']	= '文件已存在';
			$this->ajaxResult['file_id']= $res->file_id;
		} else {
			$this->ajaxResult['status'] = 1;
		}
		
		$this->render();
	}
	
	/**
	 * 分片上传文件
	 * @return mixed
	 */
	public function uploadChunk(){
		$return_info= $this->request->request('info', '', 'trim');
		$tmp_dir	= $this->base . 'temp_file';	
		$ext		= $this->request->request('ext', '', 'trim');
		$file_type	= $this->getFileType($ext);
		$upload_dir = $this->base . $file_type . DIRECTORY_SEPARATOR . self::makeDateDir();
		
		if (!file_exists($upload_dir)) {
			mkdir($upload_dir, 0755, true);
		}
		if (!file_exists($tmp_dir)) {
			mkdir($tmp_dir, 0755, true);
		}
		
		$result		= $this->_doUploadChunk($tmp_dir, $upload_dir);
		if($result['result'] == 1 && $result['done']){
			//原文件名
			$orgin_name		= '';
			$tmp_name		= $this->request->request('name', '', 'trim');
			$tmp_file		= $this->request->file('file');
			if($tmp_name){
				$orgin_name = $tmp_name;
			} else if(isset($tmp_file['name'])){
				$orgin_name = $tmp_file['name'];
			}
			$new_name		= $this->makeDateName();			
			$rename			= $this->base .$file_type . DIRECTORY_SEPARATOR . $new_name . '.' . $ext;		
			@rename($result['uploadPath'], $rename);
			
			$insert			= array(
				'name'			=> basename($rename),
				'origin_name'	=> $orgin_name,
				'md5'			=> $result['fileName'],
				'size'			=> filesize($rename),
				'ext'			=> $ext,
				'type'			=> $file_type,
				'absolute_path'	=> realpath($rename),
				'relative_path'	=> $rename
			);
			
			$res = $this->insertDb($insert)->toArray();
			if($res){
				$this->ajaxResult['status'] = 1;
				$this->ajaxResult['data']	= $res;
				
				$method = isset($this->returnInfo[$return_info]) ? $this->returnInfo[$return_info] : null;
				if(method_exists($this, $method)){
					$return = $this->$method($res['file_id'], $res, true);
					$this->ajaxResult = array_merge($this->ajaxResult, $return);
				}
			}
		}		
		$this->render();
	}
	
	private function _doUploadChunk($tmp_dir = '', $upload_dir = ''){
		// Make sure file is not cached (as it happens for example on iOS devices)
		header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
		header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
		header("Cache-Control: no-store, no-cache, must-revalidate");
		header("Cache-Control: post-check=0, pre-check=0", false);
		header("Pragma: no-cache");

		// Support CORS
		// header("Access-Control-Allow-Origin: *");
		// other CORS headers if any...
		if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
			exit; // finish preflight CORS requests here
		}

		if ( !empty($_REQUEST[ 'debug' ]) ) {
			$random = rand(0, intval($_REQUEST[ 'debug' ]) );
			if ( $random === 0 ) {
				header("HTTP/1.0 500 Internal Server Error");
				exit;
			}
		}
		// header("HTTP/1.0 500 Internal Server Error");
		// exit;

		// 5 minutes execution time
		@set_time_limit(5 * 60);

		// Uncomment this one to fake upload time
		// usleep(5000);

		//临时目录，分块文件暂存目录
		$targetDir = $tmp_dir; 
		//上传目录，分块文件上传完毕后，将分块文件合并成完整文件后的存放目录
		$uploadDir = $upload_dir;

		$cleanupTargetDir = true; // Remove old files
		$maxFileAge = 5 * 3600; // 临时文件生存时间（分块接收文件时临时文件存放路径）
		
		$return = array(
			'jsonrpc' => '2.0',
			'id'=>'id',
			'error'=>array(
				'code' => 1,
				'message' =>''
			)
		);  //结果返回

		// Create target dir
//		if (!file_exists($targetDir)) {
//			@mkdir($targetDir);
//		}
//
//		// Create target dir
//		if (!file_exists($uploadDir)) {
//			@mkdir($uploadDir);
//		}

		//以文件的md5值命名

		if (isset($_REQUEST["md5"])) {
			$fileName = $_REQUEST["md5"];
		} elseif (!empty($_FILES)) {
			$fileName = $_FILES["file"]["md5"];
		} else {
			$fileName = uniqid("file_");
		}
		
		
		$filePath = $targetDir . DIRECTORY_SEPARATOR . $fileName; 		
		$uploadPath = $uploadDir . DIRECTORY_SEPARATOR . $fileName;

		// Chunking might be enabled
		$chunk = isset($_REQUEST["chunk"]) ? intval($_REQUEST["chunk"]) : 0;
		$chunks = isset($_REQUEST["chunks"]) ? intval($_REQUEST["chunks"]) : 1;

		// Remove old temp files
		if ($cleanupTargetDir) {
			if (!is_dir($targetDir) || !$dir = opendir($targetDir)) {
				$return['error']['code'] = 100;
				$return['error']['message'] = 'Failed to open temp directory';
				return $return;
			}

			while (($file = readdir($dir)) !== false) {
				$tmpfilePath = $targetDir . DIRECTORY_SEPARATOR . $file;

				// If temp file is current file proceed to the next
				if ($tmpfilePath == "{$filePath}_{$chunk}.part" || $tmpfilePath == "{$filePath}_{$chunk}.parttmp") {
					continue;
				}

				// Remove temp file if it is older than the max age and is not the current file
				if (preg_match('/\.(part|parttmp)$/', $file) && (@filemtime($tmpfilePath) < time() - $maxFileAge)) {
					@unlink($tmpfilePath);
				}
			}
			closedir($dir);
		}

		// Open temp file
		if (!$out = @fopen("{$filePath}_{$chunk}.parttmp", "wb")) {
			$return['error']['code'] = 102;
			$return['error']['message'] = 'Failed to open output stream';
			return $return;
		}

		if (!empty($_FILES)) {
			if ($_FILES["file"]["error"] || !is_uploaded_file($_FILES["file"]["tmp_name"])) {
				$return['error']['code'] = 103;
				$return['error']['message'] = 'Failed to move uploaded file';
				return $return;
			}

			// Read binary input stream and append it to temp file
			if (!$in = @fopen($_FILES["file"]["tmp_name"], "rb")) {
				$return['error']['code'] = 101;
				$return['error']['message'] = 'Failed to open input stream.';
				return $return;
			}
		} else {
			if (!$in = @fopen("php://input", "rb")) {
				$return['error']['code'] = 101;
				$return['error']['message'] = 'Failed to open input stream.';
				return $return;
			}
		}

		while ($buff = fread($in, 4096)) {
			fwrite($out, $buff);
		}

		@fclose($out);
		@fclose($in);

		rename("{$filePath}_{$chunk}.parttmp", "{$filePath}_{$chunk}.part");

		$index = 0;
		$done = true;
		for( $index = 0; $index < $chunks; $index++ ) {
			if ( !file_exists("{$filePath}_{$index}.part") ) {
				$done = false;
				break;
			}
		}
		if ( $done ) {
			if (!$out = @fopen($uploadPath, "wb")) {
				return '{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream."}, "id" : "id"}';
			}

			if ( flock($out, LOCK_EX) ) {
				for( $index = 0; $index < $chunks; $index++ ) {
					if (!$in = @fopen("{$filePath}_{$index}.part", "rb")) {
						break;
					}

					while ($buff = fread($in, 4096)) {
						fwrite($out, $buff);
					}

					@fclose($in);
					@unlink("{$filePath}_{$index}.part");
				}

				flock($out, LOCK_UN);
			}
			@fclose($out);
			//全部上传完毕
			return array('result'=>1, 'done'=>true, 'fileName'=>$fileName, 'uploadPath'=>$uploadPath);
		}

		// 正确接收数据
		return array('result'=>1, 'done'=>false, 'fileName'=>$fileName, 'uploadPath'=>$uploadPath);
	}
	

	
	public function parseApk($file_id = 0, $data = array(), $return = false){
		if(!$file_id){		
			$file_id	= $this->request->request('file_id', '', 'intval');
		}
		if(!$data){
			$data		= FileModel::get($file_id)->toArray();
		}
		
		$result = null;
		if($data){
			$result					= getApkInfo($data['absolute_path']);
			$result['md5']			= $data['md5'];
			$result['relative_path']= $data['relative_path'];
			$result['file_id']		= $file_id;
		}
		
		if($return){
			return $result;
		} else {
			$this->render($result);
		}
	}
	
	protected function render($content = array()){
		$out = null;
		if($content){
			$out = $content;
		} else {
			$out = $this->ajaxResult;
		}

		die(json_encode($out));
	}
}
